package gupao.concurrency.concurrent.utils;

import lombok.AllArgsConstructor;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

@Slf4j
public class ArrayBlockingQueueSample {

    static final ArrayBlockingQueue<Product> productContainer = new ArrayBlockingQueue<>(10);

    static AtomicBoolean producerStopFlag = new AtomicBoolean(false);
    static AtomicBoolean consumerStopFlag = new AtomicBoolean(false);

    static long produceTimeCost = 3 * 100; //millis
    static long consumeTimeCost = 50;

    static void addOrOffer(){
        ArrayBlockingQueue<Product> q = new ArrayBlockingQueue<>(2);
        for(int i = 0; i < 10; i++){
            productContainer.offer(nextProduct());
            log.info("size: {}, remain capacity: {}", productContainer.size(), productContainer.remainingCapacity());
        }
    }

    public static void main(String[] args){
        Producer p = new Producer( producerStopFlag, produceTimeCost, ArrayBlockingQueueSample::nextProduct);
        Consumer c = new Consumer( consumerStopFlag, consumeTimeCost);

        Thread producerThread = new Thread(p, "p-1");
        Thread producerThread2 = new Thread(p, "p-2");
        Thread producerThread3 = new Thread(p, "p-3");

        Thread consumerThread = new Thread(c, "c-1");
        Thread consumerThread2 = new Thread(c, "c-2");

        setDaemonAndStart(producerThread,producerThread2,producerThread3);

        Utils.sleepIgnoreExp(1000 * 6);

        setDaemonAndStart(consumerThread,consumerThread2);

        while(Thread.activeCount() > 1){
            Thread.yield();
        }
        System.out.println("Completed");
    }

    static void setDaemonAndStart(Thread... list){
        for (Thread t : list) {
            t.setDaemon(true);
            t.start();
        }
    }
    static AtomicInteger productNum = new AtomicInteger(0);

    static Product nextProduct(){
        int x = productNum.incrementAndGet();
        if( x > 20 ){
            return Product.PILL;
        }
        return new Product(x);
    }

    @AllArgsConstructor
    @ToString
    static class Product {
        public final static Product PILL = new Product(-1);
        private int num;
    }

    @Slf4j
    static class Producer implements Runnable{

        private final AtomicBoolean stop;
        private final long timeCost;

        private final Supplier<Product> supplier;

        public Producer(AtomicBoolean stop,
                        long timeCost,
                        Supplier<Product> supplier) {
            this.stop = stop;
            this.timeCost = timeCost;
            this.supplier = supplier;
        }

        @Override
        public void run() {
            while(!stop.get()){
                Product p = supplier.get();
                Utils.sleepIgnoreExp(timeCost);
                try {
                    productContainer.put(p);
                    if(p == Product.PILL){
                        stop.set(true);
                        log.info("finished all produce task");
                    }else{
                        log.info("{} put into container, now container size: {}", p, productContainer.size());
                    }
                } catch (InterruptedException e) {
                    //ignore
                }
            }
        }
    }

    @Slf4j
    static class Consumer implements Runnable{
        private final AtomicBoolean stop;
        private final long timeCost;

        public Consumer(AtomicBoolean stop, long timeCost) {
            this.stop = stop;
            this.timeCost = timeCost;
        }

        @Override
        public void run() {
            while(!stop.get()){
                try {
                    Product p = productContainer.take();
                    if(p == Product.PILL){
                        stop.set(true);
                        log.info("consume finished");
                    }else{
                        log.info("consume product: {}, now container size: {}", p, productContainer.size());
                    }
                    Utils.sleepIgnoreExp(timeCost);
                } catch (InterruptedException e) {
                }
            }
        }
    }
}
